package net.oxygen.upload.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.oxygen.upload.config.MinioProperties;
import net.oxygen.upload.dto.FileInfoDTO;
import net.oxygen.upload.enums.BizCodeEnum;
import net.oxygen.upload.exception.BizException;
import net.oxygen.upload.mapper.FileMapper;
import net.oxygen.upload.model.FileDO;
import net.oxygen.upload.request.FileSecondUploadReq;
import net.oxygen.upload.request.FileUploadReq;
import net.oxygen.upload.service.IFileV2Service;
import net.oxygen.upload.service.IStorageService;
import net.oxygen.upload.util.CommonUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 文件服务V2实现类
 * <p>
 * 该类实现了IFileV2Service接口，提供文件的上传、秒传、查询和删除等核心功能。
 * 本实现基于MinIO对象存储服务，通过IStorageService接口与存储服务交互，
 * 将文件元数据保存在数据库中，通过FileMapper进行数据访问。
 * </p>
 * <p>
 * 主要功能包括：
 * - 小文件的直接上传
 * - 基于文件MD5特征值的秒传
 * - 文件列表查询和管理
 * - 文件删除（包括存储和数据库记录）
 * </p>
 *
 * @author chenweiwei
 * @version 2.0
 * @since 2023-03-24
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FileV2ServiceImpl implements IFileV2Service {

    /**
     * 文件数据访问对象，用于操作file表
     */
    private final FileMapper fileMapper;

    /**
     * MinIO配置属性，包含端点、访问凭证和存储桶信息
     */
    private final MinioProperties minioProperties;

    /**
     * 存储服务接口，提供与对象存储的交互能力
     */
    private final IStorageService storageService;

    /**
     * 保存文件信息到数据库
     * <p>
     * 根据上传请求和对象键创建文件记录，并插入到数据库中
     * </p>
     *
     * @param req       文件上传请求，包含文件基本信息
     * @param objectKey 文件在存储服务中的唯一键
     * @return 创建的文件数据对象
     */
    @Override
    public FileDO saveFile(FileUploadReq req, String objectKey) {
        // 构建文件数据对象，包含文件名、大小、后缀等基本信息
        FileDO fileDO = FileDO.builder()
                .fileName(req.getFileName())
                .fileSize(req.getFile() != null ? req.getFile().getSize() : req.getSize())
                .fileSuffix(CommonUtil.getFileSuffix(req.getFileName()))
                .objectKey(objectKey)
                .identifier(req.getIdentifier())
                .build();
        // 插入数据库
        fileMapper.insert(fileDO);
        return fileDO;
    }

    /**
     * 上传小文件
     * <p>
     * 将文件上传到MinIO存储服务，并在数据库中保存文件元数据
     * 如果上传失败，会抛出业务异常
     * </p>
     *
     * @param req 文件上传请求，包含MultipartFile对象和文件信息
     * @throws BizException 文件上传失败时抛出
     */
    @Override
    @Transactional
    public void upload(FileUploadReq req) {
        // 生成文件路径，格式通常为日期/文件名
        String objectKey = CommonUtil.getFilePath(req.getFileName());
        // 上传至MinIO
        boolean uploadSuccess = storageService.uploadFile(minioProperties.getBucketName(), objectKey, req.getFile());
        if (!uploadSuccess) {
            throw new BizException(BizCodeEnum.FILE_UPLOAD_FAIL);
        }
        // 存储信息至数据库
        saveFile(req, objectKey);
    }

    /**
     * 实现文件秒传功能
     * <p>
     * 根据文件的唯一标识（MD5值）查询数据库，判断文件是否已存在
     * 如果存在，则视为秒传成功，无需实际上传文件内容
     * </p>
     *
     * @param req 秒传请求，包含文件唯一标识
     * @return 是否秒传成功，true表示文件已存在可秒传，false表示需要实际上传
     */
    @Override
    public boolean secondUpload(FileSecondUploadReq req) {
        if (StrUtil.isNotBlank(req.getIdentifier())) {
            // 根据文件唯一标识查询数据库
            FileDO fileDO = fileMapper.selectOne(
                    new QueryWrapper<FileDO>()
                            .eq("identifier", req.getIdentifier()));
            // 文件已存在则返回true，表示秒传成功
            return fileDO != null;
        }
        return false;
    }

    /**
     * 获取文件列表
     * <p>
     * 根据文件名关键字查询文件列表，支持模糊匹配
     * 并将数据库实体转换为包含预签名URL的DTO对象返回
     * </p>
     *
     * @param fileName 文件名关键字，用于模糊查询，可为空
     * @param pageNum  页码，目前实现未使用，预留分页功能
     * @param pageSize 每页大小，目前实现未使用，预留分页功能
     * @return 文件信息DTO列表，包含访问URL
     */
    @Override
    public List<FileInfoDTO> list(String fileName, Integer pageNum, Integer pageSize) {
        // 构建查询条件
        QueryWrapper<FileDO> queryWrapper = new QueryWrapper<>();
        if (StrUtil.isNotBlank(fileName)) {
            queryWrapper.like("file_name", fileName);
        }
        // 按创建时间倒序排列
        queryWrapper.orderByDesc("gmt_create");

        // 查询文件列表
        List<FileDO> files = fileMapper.selectList(queryWrapper);
        // 转换为DTO对象
        return files.stream().map(file -> {
            FileInfoDTO dto = new FileInfoDTO();
            dto.setIdentifier(file.getIdentifier());
            dto.setFileName(file.getFileName());
            dto.setFileSize(file.getFileSize());
            dto.setContentType(file.getFileSuffix());
            // 生成有效期为1天的文件访问URL
            dto.setUrl(storageService.getObjectURL(minioProperties.getBucketName(), file.getObjectKey(), 1L,
                    TimeUnit.DAYS));
            // 转换时间格式
            dto.setUploadTime(
                    file.getGmtCreate().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime());
            return dto;
        }).collect(Collectors.toList());
    }

    /**
     * 删除文件
     * <p>
     * 根据文件唯一标识删除文件，包括从存储服务中删除文件对象
     * 和从数据库中删除文件记录（逻辑删除）
     * </p>
     *
     * @param identifier 文件唯一标识（MD5值）
     */
    @Override
    @Transactional
    public void delete(String identifier) {
        // 根据唯一标识查询文件
        FileDO file = fileMapper.selectOne(
                new QueryWrapper<FileDO>()
                        .eq("identifier", identifier));
        if (file != null) {
            // 从存储中删除文件
            storageService.removeObject(minioProperties.getBucketName(), file.getObjectKey());
            // 从数据库删除记录，逻辑删除
            fileMapper.deleteById(file.getId());
        }
    }
}
